package viper.main;

import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.swing.JOptionPane;

import viper.GUI.ViperGUI;
import viper.receive.CallReceiver;
import viper.receive.UDPReceiver;
import viper.send.CallSender;
import viper.send.UDPSender;

/**
 * @author Kevin Sin
 * 
 * Revision number: 2
 * Date created: 2/3/10
 * Date of Last Back-up on SVN: 2/3/10
 * 
 * <p>This class is the central class that keeps track of what a user is doing
 * - logged off, in a call, setting up a call, who the callee is...
 */
public class StateManager {
	//-----------------Singleton structure-----------------------------------
	public final static StateManager INSTANCE = new StateManager();
	private StateManager(){
		
	}
	//------------------------------------------------------------------------
	//----------------Represent state of the user-----------------------------
	public enum State {
		LOGGED_OUT, NOT_IN_CALL, DIALING, CALL_DECISION, IN_CALL 
	}
	private State state = State.LOGGED_OUT;
	
	private void setState(State state) {
		this.state = state;
	}

	public State getState() {
		return state;
	}
	//----------------------------------------------------------------
	//------------------------thread pool------------------------------
	private ExecutorService pool;
	
	/**
	 * @return pool
	 */
	public ExecutorService getPool() {
		return pool;
	}
	//----------------------------------------------------------------
	//------------------UDPSocket for receiving / sending------------
	private DatagramSocket datagramSocket; //UDP Socket
	
	/**
	 * @return datagramSocket
	 */
	public DatagramSocket getDatagramSocket() {
		return datagramSocket;
	}
	
	public static final int VIPER_PORT = 40052; //UDP port to use
	//-----------------------------------------------------------------
	//-----------------helper methods for IP addresses-----------------
	/**
	 * @return - IP address in String format
	 */
	public String getIP() {
		String ip = null;
		try {
			InetAddress address = InetAddress.getLocalHost();
			ip = address.getHostAddress();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
		return ip;
	}
	//------------------------------------------------------------------
	//-------other private variables and related methods----------------
	private String username; //name that is displayed
	private ArrayList<Object[]> peers; //peers online
	private ArrayList<Object[]> relays; //relays used in a call
	private Object[] calleeArray; //Object[] for callee	

	/**
	 * @return
	 * Getter method for username
	 */
	public String getUsername() {
		return username;
	}
	
	/**
	 * @param username
	 * Setter method for username
	 */
	public void setUsername(String username) {
		this.username = username;
	}
	
	/**
	 * @return
	 * Getter method for peers
	 */
	public ArrayList<Object[]> getPeers() {
		return peers;
	}

	/**
	 * updates the peers variable
	 * make sure you are not added yourself
	 */
	public void updatePeers() {
		ArrayList<Object[]> users = MySQLFunctions.getCurrentUsers();
		for(Object[] userArray : users)
    	{
			if(((String)userArray[0]).equals(getIP()))
			{
				users.remove(userArray);
				break;
			}
    	}
		peers = users;
	}	
	
	/**
	 * @return
	 * Getter method for relays
	 */
	public ArrayList<Object[]> getRelays() {
		return relays;
	}

	/**
	 * @param
	 * Setter method for relays
	 */
	public void setRelays(ArrayList<Object[]> relays) {
		this.relays = relays;
	}

	/**
	 * @return
	 * Getter method for calleeArray
	 */
	public Object[] getCalleeArray() {
		return calleeArray;
	}

	/**
	 * @param calleeArray
	 * Setter method for calleeArray
	 */
	public void setCalleeArray(Object[] calleeArray) {
		this.calleeArray = calleeArray;
	}
	//------------------------------------------------------------
	//--------------------State transitions-----------------------
	/**
	 * @param username
	 * Transition from LOGGED_OUT to NOT_IN_CALL
	 */
	public void login(String username){
		try{
			datagramSocket = new DatagramSocket(VIPER_PORT);
		} catch (SocketException e) {
			e.printStackTrace();
			return;
		}
		
		setState(State.NOT_IN_CALL);
		
		pool = Executors.newCachedThreadPool();
		pool.execute(new UDPReceiver()); //start UDPReceiver
		
		setUsername(username);
		MySQLFunctions.login(getIP(), username);
		ViperGUI.INSTANCE.changeCard(ViperGUI.LOGGED_IN_CARD);
		
	}
	
	/**
	 * Transitions from NOT_IN_CALL to LOGGED_OUT
	 */
	public void logout(){
		MySQLFunctions.logout(getIP());
		setState(State.LOGGED_OUT);
		ViperGUI.INSTANCE.changeCard(ViperGUI.LOGGED_OUT_CARD);
	}
	
	/**
	 * @param peerIndex which peer the user wants to talk to
	 * Transition when setting up a call after picking somebody to call.
	 */
	public void setupCall(int peerIndex){
		setCalleeArray(peers.get(peerIndex));
		ViperGUI.INSTANCE.changeCard(ViperGUI.CALL_SENDER_SETUP_CARD);
	}

	/**
	 * @param sourceAddrIP
	 * @param typeCode
	 * @param relays
	 * 
	 * State transition from NOT_IN_CALL to CALL_DECISION
	 */
	public void gotCall(String sourceAddrIP, byte typeCode, String[] relays) {
		setState(State.CALL_DECISION);
		
		int choice = JOptionPane.showOptionDialog(
				ViperGUI.INSTANCE, 
				sourceAddrIP + " wants to talk to you!", 
				"Got a call!", 
				JOptionPane.YES_NO_OPTION, 
				JOptionPane.QUESTION_MESSAGE, 
				null, 
				null, 
				0
		);
		if(choice==0) //yes
			acceptCall(sourceAddrIP, typeCode, relays);
		else //no
			rejectCall(sourceAddrIP, typeCode, relays);
	}
	
	/**
	 * State transition from CALL_DECISION to NOT_IN_CALL
	 */
	public void rejectCall(String sourceAddrIP, byte typeCode, String[] relays) {
		//send rejection packet back
		byte[] bytes = ViperHeader.makeCallSetupPacket(
				ViperHeader.SETUP_REJECT_CALL, 
				null);
		byte[] addr = UDPSender.ipStringToBytes(sourceAddrIP);
		UDPSender.sendUDP(addr, bytes);
		
		ViperGUI.INSTANCE.changeCard(ViperGUI.LOGGED_IN_CARD);
		setState(State.NOT_IN_CALL);
	}

	/**
	 * State transition from CALL_DECISION to IN_CALL.
	 * Accept call, send OK packet back
	 */
	public void acceptCall(String sourceAddrIP, byte typeCode, String[] relays){
		//send OK packet back
		byte[] bytes = ViperHeader.makeCallSetupPacket(
				ViperHeader.SETUP_ACCEPT_CALL, 
				null);
		byte[] addr = UDPSender.ipStringToBytes(sourceAddrIP);
		UDPSender.sendUDP(addr, bytes);
		
		//TODO: store typecode and relays and who is calling

		
		
		pool.execute(new CallReceiver());
		ViperGUI.INSTANCE.changeCard(ViperGUI.IN_CALL_CARD);
		setState(State.IN_CALL);
	}
	
	/**
	 * State transition from NOT_IN_CALL to DIALING
	 */
	public void dial(){
		setState(State.DIALING);
		//send setup packet
		byte[] bytes = ViperHeader.makeCallSetupPacket(
				ViperHeader.SETUP_MAKE_CALL, 
				new byte[]{ViperHeader.CODE_232} //TODO: fill in w/ the code and relays chosen
		);
		byte[] calleeAddrBytes = UDPSender.ipStringToBytes((String)calleeArray[0]);
		UDPSender.sendUDP(calleeAddrBytes, bytes);
//		JOptionPane optionPane = new JOptionPane(
//			    "The only way to close this dialog is by\n"
//			    + "pressing one of the following buttons.\n"
//			    + "Do you understand?",
//			    JOptionPane.QUESTION_MESSAGE,
//			    JOptionPane.YES_NO_OPTION
//		);
//		
//		JDialog d = optionPane.createDialog(ViperGUI.INSTANCE, "title");
//		d.setVisible(true);
		
		
		//TODO: figure out how to close dialog
		JOptionPane.showMessageDialog(
				ViperGUI.INSTANCE, 
				"Dialing", 
				"Title", 
				JOptionPane.OK_OPTION);
		cancelDial();
	}
	
	/**
	 * State transition from DIALING to NOT_IN_CALL
	 */
	public void cancelDial(){
		ViperGUI.INSTANCE.changeCard(ViperGUI.LOGGED_IN_CARD);
		setState(State.NOT_IN_CALL);
	}
	
	/**
	 * State transition from DIALING to IN_CALL
	 */
	public void dialAccepted(){		
		pool.execute(new CallSender());
		ViperGUI.INSTANCE.changeCard(ViperGUI.IN_CALL_CARD);
		setState(State.IN_CALL);
	}
	
	/**
	 * Transition from IN_CALL to NOT_IN_CALL
	 */
	public void endCall(){
		//TODO: send end call packet
		
		
		callEnded();
	}
	
	/**
	 * Used in transition from IN_CALL to NOT_IN_CALL
	 */
	public void callEnded(){
		setState(State.NOT_IN_CALL);
		ViperGUI.INSTANCE.changeCard(ViperGUI.LOGGED_IN_CARD);
	}
	//--------------------------------------------------------------
	//----------------------exiting the program---------------------
	/**
	 * called by ViperGUI -  when user closes the window,
	 * make sure the user gets logged out
	 */
	public void exit() {
		if(state!=State.LOGGED_OUT)
			logout();
	}
	//----------------------------------------------------------------
}
